package com.simen.hellobytes.socket;

import com.simen.hellobytes.socket.helper.MessageListener;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;

/**
 * 在IPv4网络中，224.0.0.0到239.255.255.255属于多播地址
 * 224.0.0.0   - 224.0.0.255     (224.0.0/24)  Local Network Control Block
 * 224.0.1.0   - 224.0.1.255     (224.0.1/24)  Internetwork Control Block
 * 224.0.2.0   - 224.0.255.0                   AD-HOC Block
 * 224.1.0.0   - 224.1.255.255   (224.1/16)    ST Multicast Groups
 * 224.2.0.0   - 224.2.255.255   (224.2/16)    SDP/SAP Block
 * 224.252.0.0 - 224.255.255.255               DIS Transient Block
 * 225.0.0.0   - 231.255.255.255               RESERVED
 * 232.0.0.0   - 232.255.255.255 (232/8)       Source Specific Multicast Block
 * 233.0.0.0   - 233.255.255.255 (233/8)       GLOP Block
 * 234.0.0.0   - 238.255.255.255               RESERVED
 * 239.0.0.0   - 239.255.255.255 (239/8)       Administratively Scoped Block
 * <p>
 * read: https://tools.ietf.org/html/rfc3171
 * read: https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml
 * read: https://tools.ietf.org/html/rfc1112
 *
 * @author Created by Simen.
 * @date 创建日期 2018/5/9 11:25
 * @modify 修改者 Simen
 */
public class UDPMultiSocket extends AbstractSocket {

    private static final String multicastHost = "239.9.9.9";
    private static final int multicastPort = 9983;
    private static final int TTL = 32;

    private String group = multicastHost;
    private int port = multicastPort;

    private MulticastSocket ms = null;
    private DatagramPacket dataPacket = null;

    private byte[] buf;
    private MessageListener<byte[],DatagramPacket> messageListener = null;
    private boolean isRunning = false;
    private InetAddress joinedGroupAddress = null;
    /**
     * 多网卡（网路）时指定网卡（网路）
     */
    private NetworkInterface networkInterface;

    public UDPMultiSocket() {
    }

    public UDPMultiSocket(String group) {
        this.group = group;
    }

    public UDPMultiSocket(String group, int port) {
        this.group = group;
        this.port = port;
    }

    public UDPMultiSocket(String group, int port, byte[] messageBytes) {
        this.group = group;
        this.port = port;
        this.buf = messageBytes;
    }

    /**
     * 启动Server端
     */
    public void listen() {
        isRunning = true;

        InetAddress groupAddress = null;
        try {
            groupAddress = InetAddress.getByName(group);

            ms = new MulticastSocket(port);
            ms.setTimeToLive(TTL);

            if (networkInterface != null) ms.setNetworkInterface(networkInterface);

            ms.joinGroup(groupAddress);
            joinedGroupAddress = groupAddress;

            buf = new byte[bufSize];

            while (isRunning) {
                DatagramPacket request = new DatagramPacket(buf, buf.length);
                ms.receive(request);

                byte[] data = null;
                int recL = request.getLength();

                if (recL != 0) {
                    data = new byte[recL];

                    System.arraycopy(buf, request.getOffset(), data, 0, recL);
                }

                if (messageListener == null
                        || (messageListener.onMessage(data, recL, request)) == MessageListener.DISCONNECT) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeImmediately();
        }

        isRunning = false;
    }

    /**
     * 启动Client端
     */
    public void sendMulticast() {
        isRunning = true;

        if (buf != null) {
            InetAddress groupAddress = null;
            try {
                groupAddress = InetAddress.getByName(group);

                ms = new MulticastSocket(port);
                ms.setTimeToLive(TTL);

                if (networkInterface != null) ms.setNetworkInterface(networkInterface);

                ms.joinGroup(groupAddress);
                joinedGroupAddress = groupAddress;

                dataPacket = new DatagramPacket(buf, buf.length, groupAddress, port);
                ms.send(dataPacket);

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                closeImmediately();
            }
        }

        isRunning = false;
    }

    public MessageListener<byte[],DatagramPacket> getMessageListener() {
        return messageListener;
    }

    public UDPMultiSocket setMessageListener(MessageListener<byte[],DatagramPacket> messageListener) {
        this.messageListener = messageListener;
        return this;
    }

    public byte[] getMessageBytes() {
        return buf;
    }

    public UDPMultiSocket setMessageBytes(byte[] messageBytes) {
        this.buf = messageBytes;
        return this;
    }

    public InetAddress getJoinedGroupAddress() {
        return joinedGroupAddress;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public UDPMultiSocket setRunning(boolean running) {
        isRunning = running;
        return this;
    }

    public UDPMultiSocket setNetworkInterface(NetworkInterface networkInterface) {
        this.networkInterface = networkInterface;
        return this;
    }

    public NetworkInterface getNetworkInterface() {
        return networkInterface;
    }

    public UDPMultiSocket closeImmediately() {
        if (ms != null) {
            if (joinedGroupAddress != null) {
                try {
                    ms.leaveGroup(joinedGroupAddress);
                } catch (IOException e) {
                }
            }
            ms.close();
            ms = null;
        }
        return this;
    }
}
